致远OA A8.0 SP2 Ajax.do调用formulaManager任意文件上传漏洞分析
漏洞环境
致远A8.0 SP2 (需要登录)
漏洞复现
1 | POST /seeyon/ajax.do?method=ajaxAction&managerName=formulaManager&requestCompress=gzip HTTP/1.1 |
天蝎连接 http://10.0.103.5/mzr2.jsp
sky
漏洞分析
通过EXP可以知道请求了ajax.do
这个路由,全局搜索ajax.do
找到映射的类
跟入到com.seeyon.ctp.common.service.AjaxController
类中,从请求参数method=ajaxAction
可以知道调用了AjaxController
的ajaxAction
方法,接下来分析ajaxAction
方法。
1 | public ModelAndView ajaxAction(HttpServletRequest request, HttpServletResponse response) throws Exception { |
这个方法主要作用是接收一个responseCompress
,这个参数作用是用于compressResponse
压缩的方式,然后调用this.invokeService
方法,在不传入ClientRequestPath
参数的情况下,对得到的结果进行压缩并返回到前端。
跟入this.invokeService
中。
1 | private String invokeService(HttpServletRequest request, HttpServletResponse response) throws BusinessException { |
invokeService
方法中首先从request
中接收了serviceName
、methodName
、strArgs
、compressType
等参数。调用了ZipUtil.uncompressRequest
对strArgs
进行解压缩,解压缩的格式为传入的compressType
。
接着用serviceName
作为参数调用了getService
方法,跟入到getService
方法中
1 | private static Object getService(String serviceName) throws Exception { |
这个方法作用就是根据serviceName
获取到对应类的已经创建好的实例,但是不能随便获取,必须是已经在配置文件中注册过的才能获取到。大概有八百多个都可以直接调用,但所包含的类中的父类及以上类不能是DataSource
、Session
、SessionFactory
三个类。
请求体传入的为managerName=formulaManager
,所获取到的对象就为formulaManager
类的实例。
接着返回到invokeService
方法中。
在122
行
调用了this.invokeMethod(service, methodName, strArgs, serviceName)
,从方法名大概就猜得到到反射调用了service
类的methodName
方法。
跟入this.invokeMethod
1 | private Object invokeMethod(Object service, String methodName, String strArgs, String serviceName) throws Exception { |
这个方法中,首先将传入的strArgs
参数解析成了Object
对象,接着判断这个对象是否为List
子类的实例,是的话会将Object
强制转换成List
对象,否则就会创建一个ArrayList
对象。然后将传入的Object添加到ArrayList
中。
经接着会serviceName + "_" + methodName + "_" + argsNum
作为键值在this.candidateMethodCache
中获取已经缓存的方法。如果获取到则会将传入的strArgs
和调用this.candidateMethodCache.get(key)
的返回值作为参数调用this.findMethodAndArgs((List)l, (List)list)
。
如果缓存中没有获取到对应的方法,那么会调用this.judgeCandidate(service, methodName, argsNum)
方法反射获取到对应的方法。
接着,跟入this.findMethodAndArgs
方法
1 | private Map findMethodAndArgs(List paramObject, List<MethodCache> candidateMethods) { |
candidateMethods
列表保存的为根据特定条件或者是缓存中获取到的方法集合,这个方法的作用其实就是从candidateMethods
列表中,根据传入的paramObject
参数列表获取到对应的方法和参数值,会根据参数的值解析成对应的对象。
接着在 464 行 执行了反射调用
以上就是Ajax.do
接口反射调用任意类任意方法及指定参数的分析过程。
通过ajax.do
接口指定method
参数为ajaxAction
调用AjaxController
类的ajaxAction
方法,这个方法中又调用了AjaxController
的invokeService
方法,这个方法可以根据类名
获取到该类的一个实例,进而调用了AjaxController
的invokeMethod
方法,进而反射调用该实例的方法。在这个过程中,反射调用所需的类名
、方法名
、参数
都可以从request
中获取到。要进行进一步利用需要找到存在一些危险操作的方法进行利用,如文件上传、文件写入、命令执行、代码执行等,如本次利用到的FormulaManagerImpl
这个类。
FormulaManagerImpl分析
FormulaManagerImpl.validate(Formula formula, String expression, Map context, boolean isSave)
调用了FormulaUtil.validate
,跟入
com.seeyon.ctp.common.formula.FormulaUtil#validate(com.seeyon.ctp.common.po.formula.Formula, java.lang.String, java.util.Map, boolean)
1 | public static boolean validate(Formula formula, String expression, Map context, boolean isSave) throws BusinessException { |
1 | public static Object eval(String scriptText, Map context) throws ScriptException, BusinessException { |
这个方法的作用就是构造groovy
脚本,调用eval
方法执行。
要利用FormulaUtil.validate
方法,需要传入Formula
对象、String expression
, Map context
, boolean isSave
。
需要绕过的几个限制
1 | if (formula.getFormulaType() != FormulaType.GroovyFunction.getKey() && formula.getFormulaType() != FormulaType.Variable.getKey()) { |
在执行到eval方法前,Formula
对象的formulaType
值要为1
或者2
才不会return
出去。
在eval
方法中执行了log
方法
1 | private static void log(String script) throws BusinessException { |
log方法主要是对脚本中的内容进行检测,不运行字符串中出现如下字符,但是不够严谨,可以用其他方法代替黑名单中的方法。
因为是调用的formula.toString
方法
可以通过this.formulaExpression
来传入我们想要执行的代码。
最后根据如上条件,我写了一个生成arguments
参数的方法,可在payload
自定义想要执行的代码。
1 | package com.example.Test; |